/*
 * @Author: czy
 * @Date: 2021-05-01 10:43:35
 * @LastEditTime: 2021-05-21 16:30:36
 */
#include "const.h"
#include "string.h"
#include "vsprintf.h"
#include "datetime.h"

static char *month_str[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec"};
static char *day_str[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
static char *complete_day_str[] = {"Saturday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
static char *complete_month_str[] = {"January", "February", "March", "April ", "May", "June", "July", "August", "September", " October", "November", "December"};
static const tm BASE_DATA = {0, 0, 0, 1, 0, 1970, 4, 0};

// Judge whether it's a leap year.
int leap_year(int year)
{
    if (((year % 100 != 0) && (year % 4 == 0)) || (year % 400 == 0))
    {
        return TRUE;
    }
    else
    {
        return FALSE;
    }
}

// Gets the number of days in the month.
int month_day(int month, int year)
{
    enum MONTH mon;
    mon = (enum MONTH)month;
    switch (mon)
    {
    case Jan:
    case Mar:
    case May:
    case Jul:
    case Aug:
    case Oct:
    case Dec:
        return 31;
    case Feb:
        if (leap_year(year))
        {
            return 29;
        }
        else
        {
            return 28;
        }
    default:
        return 30;
    }
}

void localtime(const time_t *timer, tm *locale_t)
{
    long time = (int)*timer;
    if (time < 0)
    {
        time = 0;
    }
    locale_t->tm_sec = (int)(time % 60);
    time /= 60;
    locale_t->tm_min = (int)(time % 60);
    time /= 60;
    locale_t->tm_hour = (int)(time % 24) + TIME_ZONE;
    time /= 24;
    locale_t->tm_wday = (time + base_date().tm_wday) % 7;
    locale_t->tm_year = 1970;
    for (int year = 1970;; year++)
    {
        if (leap_year(year))
        {
            if (time >= 366)
            {
                time -= 366;
            }
            else
            {
                break;
            }
        }
        else
        {
            if (time >= 365)
            {
                time -= 365;
            }
            else
            {
                break;
            }
        }
        locale_t->tm_year += 1;
    }
    locale_t->tm_mon = 0;
    for (int month = 0;; month++)
    {
        if (time >= month_day(month, locale_t->tm_year))
        {
            time -= month_day(month, locale_t->tm_year);
            locale_t->tm_mon++;
        }
        else
        {
            break;
        } 
    }
    locale_t->tm_mday = (int)time + 1;
    locale_t->tm_msec = 0;
    return;
}

/**
 * @description: The total number of days from January 1, 1970 to the specified date.
 * @param year  Year[1970-].
 * @param month Month[0-11].
 * @param mday  Mday[1-31].
 * @return Total day.
 */
static time_t total_day(int year, int month, int mday)
{
    time_t tol_day = 0;
    for (int _year = 1970; _year < year; _year++)
    {
        if (leap_year(_year))
        {
            tol_day += 366;
        }
        else
        {
            tol_day += 365;
        }
    }
    for (int _month = 0; _month < month; _month++)
    {
        tol_day += month_day(_month, year);
    }
    tol_day += mday - 1;
    return tol_day;
}

/**
 * @description: Calculate the difference between the two dates.
 * @param date1 Start date.
 * @param date2 End date.
 * @return The date difference.
 */
time_t date_difference(tm date1, tm date2)
{
    return total_day(date2.tm_year, date2.tm_mon, date2.tm_mday) - total_day(date1.tm_year, date1.tm_mon, date1.tm_mday);
}

// Convert the time structure into a timestamp.
time_t mktime(tm datetime)
{
    time_t tol_day = total_day(datetime.tm_year, datetime.tm_mon, datetime.tm_mday);
    return datetime.tm_sec + datetime.tm_min * 60 + (datetime.tm_hour - TIME_ZONE) * (60 * 60) + tol_day * (60 * 60 * 24);
}

// Get the base date.
tm base_date(void)
{
    return BASE_DATA;
}

/**
 * @description: According to the format rules defined in format, 
 *              format the time represented by the structure timeptr and store it in str.
 * @param str       Destination string.
 * @param fmt       Format.
 * @param datetime  Time structure.
 * @return Destination string.
 */
char *strftime(char *str, char *fmt, tm datetime)
{
    char temp[100];
    strreplace(str, fmt, "%%", "%");

    // weekday
    strcpy(temp, str);
    strreplace(str, temp, "%a", day_str[datetime.tm_wday]);

    strcpy(temp, str);
    strreplace(str, temp, "%A", complete_day_str[datetime.tm_wday]);

    strcpy(temp, str);
    char weekday[5] = "";
    sprintf(weekday, "%d", datetime.tm_wday);
    strreplace(str, temp, "%w", weekday);

    // month
    strcpy(temp, str);
    strreplace(str, temp, "%b", month_str[datetime.tm_mon]);

    strcpy(temp, str);
    strreplace(str, temp, "%B", complete_month_str[datetime.tm_mon]);

    strcpy(temp, str);
    char month[5] = "";
    sprintf(month, "%.2d", datetime.tm_mon + 1);
    strreplace(str, temp, "%m", month);

    // mday
    strcpy(temp, str);
    char mday[5] = "";
    sprintf(mday, "%.2d", datetime.tm_mday);
    strreplace(str, temp, "%d", mday);

    // hour(0-23)
    strcpy(temp, str);
    char hour[5] = "";
    sprintf(hour, "%.2d", datetime.tm_hour);
    strreplace(str, temp, "%H", hour);

    // hour(01-12)
    strcpy(temp, str);
    char ihour[5] = "";
    sprintf(ihour, "%.2d", datetime.tm_hour % 12 == 0 ? 12 : datetime.tm_hour % 12);
    strreplace(str, temp, "%I", ihour);

    // yday
    strcpy(temp, str);
    int tol_day = 0;
    for (int month = 0; month < datetime.tm_mon; month++)
    {
        tol_day += month_day(month, datetime.tm_year);
    }
    tol_day += datetime.tm_mday - 1;
    char yday[5] = "";
    sprintf(yday, "%.3d", tol_day);
    strreplace(str, temp, "%j", yday);

    // minute
    strcpy(temp, str);
    char minute[5] = "";
    sprintf(minute, "%.2d", datetime.tm_min);
    strreplace(str, temp, "%M", minute);

    // AM or PM
    strcpy(temp, str);
    strreplace(str, temp, "%p", datetime.tm_hour >= 12 ? "PM" : "AM");

    // second
    strcpy(temp, str);
    char second[5] = "";
    sprintf(second, "%.2d", datetime.tm_sec);
    strreplace(str, temp, "%S", second);

    // timestamp
    strcpy(temp, str);
    char timestamp[15] = "";
    sprintf(timestamp, "%ld", (long)mktime(datetime));
    strreplace(str, temp, "%s", timestamp);

    // year
    strcpy(temp, str);
    char year[5] = "";
    sprintf(year, "%.2d", datetime.tm_year % 100);
    strreplace(str, temp, "%y", year);

    strcpy(temp, str);
    char lyear[5] = "";
    sprintf(lyear, "%.4d", datetime.tm_year);
    strreplace(str, temp, "%Y", lyear);

    // date and time (e.g. Sun Aug 19 02:56:02 2012)
    strcpy(temp, str);
    char date_and_time[100] = "";
    sprintf(date_and_time, "%s %s %.2d %.2d:%.2d:%.2d %.4d",
            day_str[datetime.tm_wday], month_str[datetime.tm_mon],
            datetime.tm_mday, datetime.tm_hour, datetime.tm_min,
            datetime.tm_sec, datetime.tm_year);
    strreplace(str, temp, "%c", date_and_time);

    // date
    strcpy(temp, str);
    char date[30] = "";
    sprintf(date, "%.2d/%.2d/%.2d", datetime.tm_year % 100, datetime.tm_mon + 1, datetime.tm_mday);
    strreplace(str, temp, "%x", date);

    // time
    strcpy(temp, str);
    char time[30] = "";
    sprintf(time, "%.2d:%.2d:%.2d", datetime.tm_hour, datetime.tm_min, datetime.tm_sec);
    strreplace(str, temp, "%X", time);
    return str;
}
